package com.wbs.rest.executor;

import com.wbs.rest.config.SqlConst;
import com.wbs.rest.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpMethod;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.util.StringUtils;

import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.*;
import java.util.stream.Collectors;

/**
 *
 */
public class JdbcSqlExecutor implements SqlExecutor {

    Logger log = LoggerFactory.getLogger(JdbcSqlExecutor.class);

    JdbcTemplate jdbcTemplate;




    public JdbcSqlExecutor(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }


    @Override
    public Object execute(SqlModel sqlModel, SqlModelEnum modelEnum, HttpMethod method) throws Exception {

        Object result = null;

        if( modelEnum == SqlModelEnum.PAGE ){
            result = this.selectPage((PageQuerySqlModel) sqlModel);
        }
        else if( modelEnum == SqlModelEnum.BATCH ){
            result = this.batchInsert((BatchSqlModel) sqlModel);
        }
//        else if( modelEnum == SqlModelEnum.MULTI && HttpMethod.GET == method){
//            result = this.multiSelect((MultiSqlModel) sqlModel);
//        }
        else if( modelEnum == SqlModelEnum.MULTI && HttpMethod.PUT == method){
            result = this.multiInsert((MultiSqlModel) sqlModel);
        }
        else if( modelEnum == SqlModelEnum.MULTI && HttpMethod.POST == method){
            if("select".equals(sqlModel.getSqlType())){
                result = this.multiSelect((MultiSqlModel) sqlModel);
            }
            else if("update".equals(sqlModel.getSqlType())){
                result = this.multiUpdate((MultiSqlModel) sqlModel);
            }
        }
        else {

            if(HttpMethod.GET == method){
                result = this.select(sqlModel);
            }
            else if(HttpMethod.PUT == method){
                result = this.insert(sqlModel);
            }
            else if(HttpMethod.POST == method){
                result = this.update(sqlModel);
            }
            else if(HttpMethod.DELETE == method){
                result = this.delete(sqlModel);
            }
            else{
                throw new Exception("no mapping http method & model enum !");
            }

        }

        return result;
    }

    @Override
    public Collection select(SqlModel sqlModel){
        if(sqlModel.getArgs() == null){
            return jdbcTemplate.queryForList(sqlModel.getSql());
        }else{
            return jdbcTemplate.queryForList(sqlModel.getSql(), sqlModel.getArgs());
        }
    }


    @Override
    public List<Collection> multiSelect(MultiSqlModel multiSqlModel) {
        List<Collection> result = new ArrayList<>();

        for(SqlModel sqlModel : multiSqlModel.getSqlModels()){
            result.add(this.select(sqlModel));
        }

        return result;
    }

    @Override
    public PageData selectPage(PageQuerySqlModel pageQuerySqlModel) {
        PageData pageData = new PageData();

        //mysql
        int start = (pageQuerySqlModel.getPage() - 1) * pageQuerySqlModel.getSize();
        int limit = pageQuerySqlModel.getSize();

        StringBuilder dataSql = new StringBuilder("select * from (");
        dataSql.append(pageQuerySqlModel.getSql());
        dataSql.append(") _tmp_data limit ?, ?");

        Object[] originArgs = pageQuerySqlModel.getArgs();
        Object[] args = new Object[originArgs.length + 2];
        System.arraycopy(originArgs, 0, args, 0, originArgs.length);
        args[originArgs.length] = start;
        args[originArgs.length+1] = limit;

        pageData.setData(jdbcTemplate.queryForList(dataSql.toString(), args));

        StringBuilder countSql = new StringBuilder("select count(*) from (");
        countSql.append(pageQuerySqlModel.getSql());
        countSql.append(") _tmp_count");

        pageData.setTotal(jdbcTemplate.queryForObject(countSql.toString(), originArgs, Long.class));

        return pageData;
    }

    @Override
    public Map<String, Object> selectOne(SqlModel sqlModel) {
        if(sqlModel.getArgs() == null){
            return jdbcTemplate.queryForMap(sqlModel.getSql());
        }else{
            try{
                return jdbcTemplate.queryForMap(sqlModel.getSql(), sqlModel.getArgs());
            }catch (EmptyResultDataAccessException e){
                return new HashMap<>();
            }

        }
    }


    @Override
    public int update(SqlModel sqlModel){
        if(sqlModel.getArgs() == null){
            return jdbcTemplate.update(sqlModel.getSql());
        }else{
            return jdbcTemplate.update(sqlModel.getSql(), sqlModel.getArgs());
        }
    }

    @Override
    public int[] multiUpdate(MultiSqlModel multiSqlModel){
        List<SqlModel> models = multiSqlModel.getSqlModels();
        int[] result = new int[models.size()];
        for(int i=0; i<models.size(); i++){
            result[i] = update(models.get(i));
        }
        return result;
    }

    @Override
    public int insert(SqlModel sqlModel){
        if(sqlModel.getArgs() == null){
            return jdbcTemplate.update(sqlModel.getSql());
        }else{
            return jdbcTemplate.update(sqlModel.getSql(), sqlModel.getArgs());
        }
    }

    @Override
    public int[] batchInsert(BatchSqlModel batchSqlModel) {
        if(batchSqlModel.getArgsList() == null){
            return jdbcTemplate.batchUpdate(batchSqlModel.getSql());
        }else{
            return jdbcTemplate.batchUpdate(batchSqlModel.getSql(), batchSqlModel.getArgsList());
        }
    }

    @Override
    public int[] multiInsert(MultiSqlModel multiSqlModel) {

        List<SqlModel> models = multiSqlModel.getSqlModels();
        int[] result = new int[models.size()];

        Map<String, Object> referenceParams = multiSqlModel.getReferenceParams();

        for(int i=0; i<models.size(); i++){
            SqlModel model = models.get(i);

            //更新依赖主键参数
            for (int j = 0; j < model.getArgNames().size(); j++) {
                String argName = model.getArgNames().get(j);
                if(SqlConst.KEY_UUID.equals(argName)){
                    model.getArgs()[j] = UUID.randomUUID().toString();
                }
                else if(referenceParams.containsKey(argName)){
                    model.getArgs()[j] = referenceParams.get(argName);
                }
            }

            if(model.getSqlKeyEnum() == SqlKeyEnum.INCREASE){

                KeyHolder keyHolder = new GeneratedKeyHolder();
                PreparedStatementCreator psc = con -> {
                    PreparedStatement ps = con.prepareStatement(model.getSql(), Statement.RETURN_GENERATED_KEYS);
                    for (int k = 1; k <= model.getArgs().length; k++) {
                        ps.setObject(k, model.getArgs()[k-1]);
                    }
                    return ps;
                };

                result[i] = jdbcTemplate.update(psc, keyHolder);
                referenceParams.put(SqlConst.KEY_INCREASE_PREFIX + model.getId(), keyHolder.getKey().longValue());

            }else{
                result[i] = update(model);
            }
        }


        return result;
    }

    @Override
    public int delete(SqlModel sqlModel){
        if(sqlModel.getArgs() == null){
            return jdbcTemplate.update(sqlModel.getSql());
        }else{
            return jdbcTemplate.update(sqlModel.getSql(), sqlModel.getArgs());
        }
    }


}
